Basic Usage

To create a {gt} table, use gt() on a data frame.

library(gt)
library(dplyr)
gt(head(mtcars))
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
# pipe also works just fine!
head(mtcars) %>% 
  gt()
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Preview long table

Sometimes you may want to see just a small portion of your input data. We can use gt_preview() in place of gt() to get the first x rows of data and the last y rows of data (which can be set by the top_n and bottom_n arguments).

gtcars %>%
  dplyr::select(mfr, model, year) %>%
  gt_preview()
mfr model year
1 Ford GT 2017
2 Ferrari 458 Speciale 2015
3 Ferrari 458 Spider 2015
4 Ferrari 458 Italia 2014
5 Ferrari 488 GTB 2016
6..46
47 Rolls-Royce Wraith 2016

Grouping and Summary Rows

You can group rows in a table by specifying one or more columns in groupname_col:

head(mtcars) %>% 
  mutate(cyl = paste(cyl, "Cylinders")) %>% 
  gt(groupname_col = "cyl")
mpg disp hp drat wt qsec vs am gear carb
6 Cylinders
21.0 160 110 3.90 2.620 16.46 0 1 4 4
21.0 160 110 3.90 2.875 17.02 0 1 4 4
21.4 258 110 3.08 3.215 19.44 1 0 3 1
18.1 225 105 2.76 3.460 20.22 1 0 3 1
4 Cylinders
22.8 108 93 3.85 2.320 18.61 1 1 4 1
8 Cylinders
18.7 360 175 3.15 3.440 17.02 0 0 3 2

Or by simply using dplyr::group_by()

head(mtcars) %>% 
  mutate(cyl = paste(cyl, "Cylinders")) %>% 
  group_by(cyl) %>% 
  gt()
mpg disp hp drat wt qsec vs am gear carb
6 Cylinders
21.0 160 110 3.90 2.620 16.46 0 1 4 4
21.0 160 110 3.90 2.875 17.02 0 1 4 4
21.4 258 110 3.08 3.215 19.44 1 0 3 1
18.1 225 105 2.76 3.460 20.22 1 0 3 1
4 Cylinders
22.8 108 93 3.85 2.320 18.61 1 1 4 1
8 Cylinders
18.7 360 175 3.15 3.440 17.02 0 0 3 2

Custom groups

You can also create custom groups with gt::tab_row_group(). This is typically useful for creating your own groups within gt itself, and it can include specific rows based on a logical statement (ie hp > 600).

gtcars %>%
  dplyr::select(model, year, hp, trq) %>%
  head(8) %>% 
  gt() %>% 
  tab_row_group(
    group = "powerful",
    rows = hp <= 600
  ) %>%
  tab_row_group(
    group = "super powerful",
    rows = hp > 600
  )
model year hp trq
super powerful
GT 2017 647 550
488 GTB 2016 661 561
GTC4Lusso 2017 680 514
FF 2015 652 504
powerful
458 Speciale 2015 597 398
458 Spider 2015 562 398
458 Italia 2014 562 398
California 2015 553 557

You can also create meta-groups of a grouping category this way.

gtcars %>% 
  dplyr::select(mfr:hp, mpg_c, mpg_h) %>% 
  dplyr::filter(mfr %in% c("Ford", "Dodge", "Chevrolet", "Nissan", "Acura")) %>% 
  gt() %>% 
  tab_row_group(
    group = "Japanese",
    rows = mfr %in% c("Nissan", "Acura")
  ) %>% 
  tab_row_group(
    group = "American",
    rows = mfr %in% c("Ford", "Dodge", "Chevrolet")
  )
mfr model year trim bdy_style hp mpg_c mpg_h
American
Ford GT 2017 Base Coupe coupe 647 11 18
Chevrolet Corvette 2016 Z06 Coupe coupe 650 15 22
Dodge Viper 2017 GT Coupe coupe 645 12 19
Japanese
Acura NSX 2017 Base Coupe coupe 573 21 22
Nissan GT-R 2016 Premium Coupe coupe 545 16 22

Row names

You can also convert a column into table rownames and specify it in the original gt() call.

head(mtcars) %>% 
  mutate(cyl = paste(cyl, "Cylinders")) %>% 
  gt(rowname_col = "cyl")
mpg disp hp drat wt qsec vs am gear carb
6 Cylinders 21.0 160 110 3.90 2.620 16.46 0 1 4 4
6 Cylinders 21.0 160 110 3.90 2.875 17.02 0 1 4 4
4 Cylinders 22.8 108 93 3.85 2.320 18.61 1 1 4 1
6 Cylinders 21.4 258 110 3.08 3.215 19.44 1 0 3 1
8 Cylinders 18.7 360 175 3.15 3.440 17.02 0 0 3 2
6 Cylinders 18.1 225 105 2.76 3.460 20.22 1 0 3 1

If you have a data.frame with rownames attached, you can use the rownames_to_stub argument to parse these properly.

head(mtcars) %>% 
  gt(rownames_to_stub = TRUE)
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

However, a tibble will drop rownames, so you can convert a data.frame’s existing rownames to a column with tibble::rownames_to_column(). gt will automatically use columns named rowname as a rowname stub.

head(mtcars) %>% 
  tibble::rownames_to_column() %>% 
  gt()
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Rownames and groups

Combining rownames with groups can sometimes make the table easier to parse. Compare the two tables below:

mtcars %>% 
  head() %>% 
  select(cyl, mpg:drat) %>% 
  mutate(cyl = paste(cyl, "Cylinders")) %>% 
  gt(groupname_col = "cyl")
mpg disp hp drat
6 Cylinders
21.0 160 110 3.90
21.0 160 110 3.90
21.4 258 110 3.08
18.1 225 105 2.76
4 Cylinders
22.8 108 93 3.85
8 Cylinders
18.7 360 175 3.15
head(mtcars) %>% 
  select(cyl, mpg:drat) %>% 
  tibble::rownames_to_column() %>% 
  mutate(cyl = paste(cyl, "Cylinders")) %>% 
  gt(groupname_col = "cyl", rowname_col = "rowname")
mpg disp hp drat
6 Cylinders
Mazda RX4 21.0 160 110 3.90
Mazda RX4 Wag 21.0 160 110 3.90
Hornet 4 Drive 21.4 258 110 3.08
Valiant 18.1 225 105 2.76
4 Cylinders
Datsun 710 22.8 108 93 3.85
8 Cylinders
Hornet Sportabout 18.7 360 175 3.15

Create blank rownames

I typically will use a rowname column whenever I group data, but sometimes there may not be a “good” column to use here. You can pass in blank spaces to artificially move the group label to be presented closer to a “stub”.

head(mtcars) %>% 
  mutate(cyl = paste(cyl, "Cylinders")) %>% 
  mutate(blank_rowname = purrr::map(list(rep("&nbsp", 8)), gt::html)) %>% 
  gt(rowname_col = "blank_rowname", groupname_col = "cyl")
mpg disp hp drat wt qsec vs am gear carb
6 Cylinders
                21.0 160 110 3.90 2.620 16.46 0 1 4 4
                21.0 160 110 3.90 2.875 17.02 0 1 4 4
                21.4 258 110 3.08 3.215 19.44 1 0 3 1
                18.1 225 105 2.76 3.460 20.22 1 0 3 1
4 Cylinders
                22.8 108 93 3.85 2.320 18.61 1 1 4 1
8 Cylinders
                18.7 360 175 3.15 3.440 17.02 0 0 3 2

Summary Rows

When rows are grouped, you can create summary rows in a column using the summary_rows function:

mtcars %>% 
  head(8) %>% 
  tibble::rownames_to_column(var = "name") %>%
  mutate(cyl = paste(cyl, "Cylinders")) %>% 
  gt(groupname_col = "cyl", rowname_col = "name") %>% 
  summary_rows(
    groups = TRUE,
    fns = list(Average = ~mean(.))
    )
mpg disp hp drat wt qsec vs am gear carb
6 Cylinders
Mazda RX4 21.0 160.0 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 160.0 110 3.90 2.875 17.02 0 1 4 4
Hornet 4 Drive 21.4 258.0 110 3.08 3.215 19.44 1 0 3 1
Valiant 18.1 225.0 105 2.76 3.460 20.22 1 0 3 1
Average 20.38 200.75 108.75 3.41 3.04 18.29 0.50 0.50 3.50 2.50
4 Cylinders
Datsun 710 22.8 108.0 93 3.85 2.320 18.61 1 1 4 1
Merc 240D 24.4 146.7 62 3.69 3.190 20.00 1 0 4 2
Average 23.60 127.35 77.50 3.77 2.75 19.30 1.00 0.50 4.00 1.50
8 Cylinders
Hornet Sportabout 18.7 360.0 175 3.15 3.440 17.02 0 0 3 2
Duster 360 14.3 360.0 245 3.21 3.570 15.84 0 0 3 4
Average 16.50 360.00 210.00 3.18 3.50 16.43 0.00 0.00 3.00 3.00

Further customization of Summary Rows

You can pass additional summarization functions to the fns argument, optionally specify columns to apply the summary to, and apply a formatter to format the output.

mtcars %>% 
  head(8) %>% 
  tibble::rownames_to_column(var = "name") %>%
  mutate(cyl = paste(cyl, "Cylinders")) %>% 
  gt(groupname_col = "cyl", rowname_col = "name") %>% 
  summary_rows(
    groups = TRUE,
    columns = vars(mpg, disp, hp),
    fns = list(
      min = ~min(.),
      max = ~max(.),
      avg = ~mean(.)
      ),
    formatter = fmt_number
    )
mpg disp hp drat wt qsec vs am gear carb
6 Cylinders
Mazda RX4 21.0 160.0 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 160.0 110 3.90 2.875 17.02 0 1 4 4
Hornet 4 Drive 21.4 258.0 110 3.08 3.215 19.44 1 0 3 1
Valiant 18.1 225.0 105 2.76 3.460 20.22 1 0 3 1
min 18.10 160.00 105.00
max 21.40 258.00 110.00
avg 20.38 200.75 108.75
4 Cylinders
Datsun 710 22.8 108.0 93 3.85 2.320 18.61 1 1 4 1
Merc 240D 24.4 146.7 62 3.69 3.190 20.00 1 0 4 2
min 22.80 108.00 62.00
max 24.40 146.70 93.00
avg 23.60 127.35 77.50
8 Cylinders
Hornet Sportabout 18.7 360.0 175 3.15 3.440 17.02 0 0 3 2
Duster 360 14.3 360.0 245 3.21 3.570 15.84 0 0 3 4
min 14.30 360.00 175.00
max 18.70 360.00 245.00
avg 16.50 360.00 210.00

You can use any of R’s built-in aggregate functions, or a custom function.

# some examples
sum()     # Sum of numbers
mean()    # Mean of numbers
max()     # Maximum of numbers
min()     # Minimum of numbers
median()  # Median of numbers
sd()      # Standard Deviation of numbers

Or a custom aggregate function:

mode <- function(x) {
  unique_var <- unique(x)
  unique_var[which.max(tabulate(match(x, unique_var)))]
}

Multiple groups

You can supply multiple groups via dplyr::group_by(), which are then appended with a - separator.

head(mtcars, 8) %>% 
  tibble::rownames_to_column(var = "name") %>%
  mutate(cyl = paste(cyl, "Cylinders")) %>% 
  group_by(cyl, gear) %>% 
  arrange(cyl) %>%
  gt(rowname_col = "name") %>% 
  summary_rows(
    groups = TRUE,
    fns = list(Average = ~mean(.))
    )
mpg disp hp drat wt qsec vs am carb
4 Cylinders - 4
Datsun 710 22.8 108.0 93 3.85 2.320 18.61 1 1 1
Merc 240D 24.4 146.7 62 3.69 3.190 20.00 1 0 2
Average 23.60 127.35 77.50 3.77 2.75 19.30 1.00 0.50 1.50
6 Cylinders - 4
Mazda RX4 21.0 160.0 110 3.90 2.620 16.46 0 1 4
Mazda RX4 Wag 21.0 160.0 110 3.90 2.875 17.02 0 1 4
Average 21.00 160.00 110.00 3.90 2.75 16.74 0.00 1.00 4.00
6 Cylinders - 3
Hornet 4 Drive 21.4 258.0 110 3.08 3.215 19.44 1 0 1
Valiant 18.1 225.0 105 2.76 3.460 20.22 1 0 1
Average 19.75 241.50 107.50 2.92 3.34 19.83 1.00 0.00 1.00
8 Cylinders - 3
Hornet Sportabout 18.7 360.0 175 3.15 3.440 17.02 0 0 2
Duster 360 14.3 360.0 245 3.21 3.570 15.84 0 0 4
Average 16.50 360.00 210.00 3.18 3.50 16.43 0.00 0.00 3.00

Grand summary

Grand summary rows incorporate all of the available data, regardless of whether some of the data are part of row groups.

head(mtcars, 8) %>% 
  tibble::rownames_to_column(var = "name") %>%
  mutate(cyl = paste(cyl, "Cylinders")) %>% 
  gt(rowname_col = "name", groupname_col = "cyl") %>% 
  grand_summary_rows(fns = list(Average = ~mean(.)))
mpg disp hp drat wt qsec vs am gear carb
6 Cylinders
Mazda RX4 21.0 160.0 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 160.0 110 3.90 2.875 17.02 0 1 4 4
Hornet 4 Drive 21.4 258.0 110 3.08 3.215 19.44 1 0 3 1
Valiant 18.1 225.0 105 2.76 3.460 20.22 1 0 3 1
4 Cylinders
Datsun 710 22.8 108.0 93 3.85 2.320 18.61 1 1 4 1
Merc 240D 24.4 146.7 62 3.69 3.190 20.00 1 0 4 2
8 Cylinders
Hornet Sportabout 18.7 360.0 175 3.15 3.440 17.02 0 0 3 2
Duster 360 14.3 360.0 245 3.21 3.570 15.84 0 0 3 4
Average 20.21 222.21 126.25 3.44 3.09 18.08 0.50 0.38 3.50 2.38

Column Formatting

You can format data in a column by using the various fmt_??? functions:

info_date_style() # View a table with info on date styles

info_time_style() # View a table with info on time styles

info_currencies() # View a table with info on supported currencies

info_locales()    # View a table with info on supported locales

exibble

The exibble dataset is built into gt and has a lot of different formats to demo the specific fmt_??? functions.

dplyr::glimpse(exibble)
## Rows: 8
## Columns: 9
## $ num      <dbl> 1.111e-01, 2.222e+00, 3.333e+01, 4.444e+02, 5.550e+03, NA, 7.770e+05, 8.880e+06
## $ char     <chr> "apricot", "banana", "coconut", "durian", NA, "fig", "grapefruit", "honeydew"
## $ fctr     <fct> one, two, three, four, five, six, seven, eight
## $ date     <chr> "2015-01-15", "2015-02-15", "2015-03-15", "2015-04-15", "2015-05-15", "2015-06-15", NA, "2015-08-15"
## $ time     <chr> "13:35", "14:40", "15:45", "16:50", "17:55", NA, "19:10", "20:20"
## $ datetime <chr> "2018-01-01 02:22", "2018-02-02 14:33", "2018-03-03 03:44", "2018-04-04 15:55", "2018-05-05 04:00", "20…
## $ currency <dbl> 49.950, 17.950, 1.390, 65100.000, 1325.810, 13.255, NA, 0.440
## $ row      <chr> "row_1", "row_2", "row_3", "row_4", "row_5", "row_6", "row_7", "row_8"
## $ group    <chr> "grp_a", "grp_a", "grp_a", "grp_a", "grp_b", "grp_b", "grp_b", "grp_b"
exibble %>% 
  gt(rowname_col = "row", groupname_col = "group") %>% 
  fmt_number(columns = vars(num)) %>% 
  fmt_date(columns = vars(date)) %>% 
  fmt_time(columns = vars(time)) %>% 
  fmt_datetime(columns = vars(datetime)) %>% 
  fmt_currency(columns = vars(currency))
num char fctr date time datetime currency
grp_a
row_1 0.11 apricot one Thursday, January 15, 2015 13:35 Monday, January 1, 2018 02:22 $49.95
row_2 2.22 banana two Sunday, February 15, 2015 14:40 Friday, February 2, 2018 14:33 $17.95
row_3 33.33 coconut three Sunday, March 15, 2015 15:45 Saturday, March 3, 2018 03:44 $1.39
row_4 444.40 durian four Wednesday, April 15, 2015 16:50 Wednesday, April 4, 2018 15:55 $65,100.00
grp_b
row_5 5,550.00 NA five Friday, May 15, 2015 17:55 Saturday, May 5, 2018 04:00 $1,325.81
row_6 NA fig six Monday, June 15, 2015 NA Wednesday, June 6, 2018 16:11 $13.26
row_7 777,000.00 grapefruit seven NA 19:10 Saturday, July 7, 2018 05:22 NA
row_8 8,880,000.00 honeydew eight Saturday, August 15, 2015 20:20 NA $0.44

To use a specific locale for data formatting, provide specific arguments to the respective functions.

Date formatting

exibble %>% 
  select(date, time, datetime) %>% 
  gt(rowname_col = "row", groupname_col = "group") %>% 
  fmt_date(columns = vars(date), date_style = 3) %>% 
  fmt_time(columns = vars(time), time_style = 5) %>% 
  fmt_datetime(columns = vars(datetime), date_style = 6, time_style = 4)
date time datetime
Thu, Jan 15, 2015 1 PM Jan 1, 2018 2:22 AM
Sun, Feb 15, 2015 2 PM Feb 2, 2018 2:33 PM
Sun, Mar 15, 2015 3 PM Mar 3, 2018 3:44 AM
Wed, Apr 15, 2015 4 PM Apr 4, 2018 3:55 PM
Fri, May 15, 2015 5 PM May 5, 2018 4:00 AM
Mon, Jun 15, 2015 NA Jun 6, 2018 4:11 PM
NA 7 PM Jul 7, 2018 5:22 AM
Sat, Aug 15, 2015 8 PM NA

Currency formatting

money <- data.frame(
  USD = c(12.12, 2141.213, 0.42, 1.55, 34414),
  EUR = c(10.68, 1884.27, 0.37, 1.36, 30284.32),
  INR = c(861.07, 152122.48, 29.84, 110, 2444942.63),
  JPY = c(1280, 226144, 44.36, 164, 3634634.61),
  MAD = c(115.78, 20453.94, 4.01, 15, 328739.73)
)

money %>%
  gt() %>%
  fmt_currency(columns = vars(USD), currency = "USD") %>%
  fmt_currency(columns = vars(EUR), currency = "EUR") %>%
  fmt_currency(columns = vars(INR), currency = "INR") %>%
  fmt_currency(columns = vars(JPY), currency = "JPY") %>%
  fmt_currency(columns = vars(MAD), currency = "MAD")
USD EUR INR JPY MAD
$12.12 €10.68 ₹861.07 ¥1,280 Dh115.78
$2,141.21 €1,884.27 ₹152,122.48 ¥226,144 Dh20,453.94
$0.42 €0.37 ₹29.84 ¥44 Dh4.01
$1.55 €1.36 ₹110.00 ¥164 Dh15.00
$34,414.00 €30,284.32 ₹2,444,942.63 ¥3,634,635 Dh328,739.73

Percent formatting

data.frame(
  x = 1:5,
  y = 6:10,
  percent = seq(from = 0.1, to =  0.2, by = 0.025)
) %>% 
  gt() %>% 
  fmt_percent(columns = vars(percent), decimals = 1)
x y percent
1 6 10.0%
2 7 12.5%
3 8 15.0%
4 9 17.5%
5 10 20.0%

Number formatting

Numeric formatting can include changes to the number of decimals, separators (ie “,”), or even suffixing (ie K, Mb, etc).

exibble %>% 
  select(group, num, currency) %>% 
  gt() %>% 
  fmt_number(columns = vars(num), decimals = 4, sep_mark = "") %>% 
  # Suffixing scale and apply suffixes to larger numbers 
  fmt_number(columns = vars(currency), decimals = 1, suffixing = TRUE)
group num currency
grp_a 0.1111 50.0
grp_a 2.2220 17.9
grp_a 33.3300 1.4
grp_a 444.4000 65.1K
grp_b 5550.0000 1.3K
grp_b NA 13.3
grp_b 777000.0000 NA
grp_b 8880000.0000 0.4

Displaying missing values

Missing values are ignored by formatters and shown as NA by default. You can specify missing values with other indicators with fmt_missing

exibble %>% 
  select(group, currency, num) %>% 
  gt() %>% 
  fmt_missing(columns = vars(currency), rows = is.na(currency)) %>% 
  fmt_missing(columns = vars(num), rows = is.na(num), missing_text = "none")
group currency num
grp_a 49.950 1.111e-01
grp_a 17.950 2.222e+00
grp_a 1.390 3.333e+01
grp_a 65100.000 4.444e+02
grp_b 1325.810 5.550e+03
grp_b 13.255 none
grp_b 7.770e+05
grp_b 0.440 8.880e+06

Format markdown

You can also parse cell content that contains arbitrary markdown.

# Create a few Markdown-based
# text snippets
text_1a <- "
### This is Markdown.

Markdown’s syntax is comprised entirely of
punctuation characters, which punctuation
characters have been carefully chosen so as
to look like what they mean... assuming
you’ve ever used email.
"

text_1b <- "
Info on Markdown syntax can be found
[here](https://daringfireball.net/projects/markdown/).
"

text_2a <- "
The **gt** package has these datasets:

 - `countrypops`
 - `sza`
 - `gtcars`
 - `sp500`
 - `pizzaplace`
 - `exibble`
"

text_2b <- "
There's a quick reference [here](https://commonmark.org/help/).
"

# Arrange the text snippets as a tibble
# using the `dplyr::tribble()` function;
# then, create a gt table and format
# all columns with `fmt_markdown()`
dplyr::tribble(
  ~Markdown, ~md,
  text_1a,   text_2a,
  text_1b,   text_2b,
  ) %>%
  gt() %>%
  fmt_markdown(columns = TRUE) %>%
  tab_options(table.width = px(400))
Markdown md

This is Markdown.

Markdown’s syntax is comprised entirely of punctuation characters, which punctuation characters have been carefully chosen so as to look like what they mean... assuming you’ve ever used email.

The gt package has these datasets:

  • countrypops
  • sza
  • gtcars
  • sp500
  • pizzaplace
  • exibble

Info on Markdown syntax can be found here.

There's a quick reference here.

Custom data formatting

If none of the built-in formatters apply to your data, you can use fmt() instead.

data.frame(
  count = c(1L, 2L, 3L, 4L, 5L),
  weight_g = c(150.65, 149.65, 171.28, 142.58, 139.04),
  color = c("green", "yellow", "yellow", "green", "yellow")
) %>% 
  gt() %>% 
  fmt(
    columns = vars(count),
    fns = function(x){ paste(x, "bananas")}
  ) 
count weight_g color
1 bananas 150.65 green
2 bananas 149.65 yellow
3 bananas 171.28 yellow
4 bananas 142.58 green
5 bananas 139.04 yellow

Create or Modify Parts

Add a header

data.frame(
  count = c(1L, 2L, 3L, 4L, 5L),
  weight_g = c(150.65, 149.65, 171.28, 142.58, 139.04),
  color = c("green", "yellow", "yellow", "green", "yellow")
) %>% 
  gt() %>% 
  tab_header(
    title = "Number of bananas, weight, and ripeness",
    subtitle = "Bananas sourced in Mar 2021"
    )
Number of bananas, weight, and ripeness
Bananas sourced in Mar 2021
count weight_g color
1 150.65 green
2 149.65 yellow
3 171.28 yellow
4 142.58 green
5 139.04 yellow

Format header

You can parse markdown with md() or HTML with html().

data.frame(
  count = c(1L, 2L, 3L, 4L, 5L),
  weight_g = c(150.65, 149.65, 171.28, 142.58, 139.04),
  color = c("green", "yellow", "yellow", "green", "yellow")
) %>% 
  gt() %>% 
  tab_header(
    title = md("**Number of bananas, weight, and ripeness**"),
    subtitle = html("Bananas sourced in <em><b>Mar 2021<b></em>")
    )
Number of bananas, weight, and ripeness
Bananas sourced in Mar 2021
count weight_g color
1 150.65 green
2 149.65 yellow
3 171.28 yellow
4 142.58 green
5 139.04 yellow

Add spanner column labels

You can create column label “groups” with the tab_spanner() function.

head(gtcars, 8) %>%
  dplyr::select(model:mpg_h, msrp) %>%  
  gt(rowname_col = "model") %>%
  tab_spanner(
    label = "Performance",
    columns = vars(hp, hp_rpm, trq, trq_rpm, mpg_c, mpg_h)
  ) %>% 
  tab_spanner(
    label = "Car Info",
    columns = vars(year, bdy_style, trim)
  )
Car Info Performance msrp
year bdy_style trim hp hp_rpm trq trq_rpm mpg_c mpg_h
GT 2017 coupe Base Coupe 647 6250 550 5900 11 18 447000
458 Speciale 2015 coupe Base Coupe 597 9000 398 6000 13 17 291744
458 Spider 2015 convertible Base 562 9000 398 6000 13 17 263553
458 Italia 2014 coupe Base Coupe 562 9000 398 6000 13 17 233509
488 GTB 2016 coupe Base Coupe 661 8000 561 3000 15 22 245400
California 2015 convertible Base Convertible 553 7500 557 4750 16 23 198973
GTC4Lusso 2017 coupe Base Coupe 680 8250 514 5750 12 17 298000
FF 2015 coupe Base Coupe 652 8000 504 6000 11 16 295000

Add spanner delim

For columns that are well formatted, gt can parse the delimiter and “split” the label into its component parts.

head(gtcars, 8) %>%
  dplyr::select(model:trim, mpg_city = mpg_c, mpg_hwy = mpg_h) %>%  
  gt(rowname_col = "model") %>%
  tab_spanner_delim(delim = "_")
year trim mpg
city hwy
GT 2017 Base Coupe 11 18
458 Speciale 2015 Base Coupe 13 17
458 Spider 2015 Base 13 17
458 Italia 2014 Base Coupe 13 17
488 GTB 2016 Base Coupe 15 22
California 2015 Base Convertible 16 23
GTC4Lusso 2017 Base Coupe 12 17
FF 2015 Base Coupe 11 16

Locations

gt usesthe locations argument across many functions to let you tightly customize specific components. These are considered “Helper Functions”, and are further expended in the gt documentation.

Locations is used with the various cells_??? functions like: cells_title, cells_stubhead, cells_column_spanners().

Cell body

For the cells_body(), it includes arguments for columns and rows, allowing you to specify specific columns or even columns + subsets of specific rows based on logicals.

head(gtcars, 8) %>%
  dplyr::select(model:trim, mpg_city = mpg_c, mpg_hwy = mpg_h) %>%  
  gt(rowname_col = "model") %>% 
  tab_style(
    style = cell_text(color = "red"),
    locations = cells_body(
      columns = vars(trim),
      rows = trim == "Base Convertible"
      )
  )
year trim mpg_city mpg_hwy
GT 2017 Base Coupe 11 18
458 Speciale 2015 Base Coupe 13 17
458 Spider 2015 Base 13 17
458 Italia 2014 Base Coupe 13 17
488 GTB 2016 Base Coupe 15 22
California 2015 Base Convertible 16 23
GTC4Lusso 2017 Base Coupe 12 17
FF 2015 Base Coupe 11 16

Table title

Table header/title can be affected by cells_title, and it can affect either the title, subtitle or both (default).

head(gtcars, 8) %>%
  dplyr::select(model:trim, mpg_city = mpg_c, mpg_hwy = mpg_h) %>%  
  gt() %>% 
  tab_header(
    title = "These are not efficient cars",
    subtitle = "But they are fast"
    ) %>% 
  tab_style(
    style = cell_text(color = "black", weight = "bold", align = "left"),
    locations = cells_title("title")
  )
These are not efficient cars
But they are fast
model year trim mpg_city mpg_hwy
GT 2017 Base Coupe 11 18
458 Speciale 2015 Base Coupe 13 17
458 Spider 2015 Base 13 17
458 Italia 2014 Base Coupe 13 17
488 GTB 2016 Base Coupe 15 22
California 2015 Base Convertible 16 23
GTC4Lusso 2017 Base Coupe 12 17
FF 2015 Base Coupe 11 16

Table stub

You can affect both the stubhead and the stub rows themselves as well.

head(gtcars, 8) %>%
  dplyr::select(model:trim, mpg_city = mpg_c, mpg_hwy = mpg_h) %>%  
  gt(rowname_col = "model") %>% 
  tab_stubhead("Car Models") %>% 
  tab_style(
    style = list(
      cell_fill("black"),
      cell_text(color = "white", weight = "bold")
      ),
    locations = cells_stubhead()
  ) %>% 
  tab_style(
    style = cell_text(color = "darkgrey", weight = "bold"),
    locations = cells_stub()
  )
Car Models year trim mpg_city mpg_hwy
GT 2017 Base Coupe 11 18
458 Speciale 2015 Base Coupe 13 17
458 Spider 2015 Base 13 17
458 Italia 2014 Base Coupe 13 17
488 GTB 2016 Base Coupe 15 22
California 2015 Base Convertible 16 23
GTC4Lusso 2017 Base Coupe 12 17
FF 2015 Base Coupe 11 16

Row group

Row groups can be further emphasized by changing the background or other styling.

gtcars %>% 
  filter(mfr %in% c("Ferrari", "Porsche")) %>% 
  dplyr::select(mfr, model:trim, mpg_city = mpg_c, mpg_hwy = mpg_h) %>%  
  gt(rowname_col = "model", groupname_col = "mfr") %>% 
  tab_style(
    style = list(
      cell_fill("black"),
      cell_text(color = "white", weight = "bold")
      ),
    locations = cells_row_groups()
  ) %>% 
  tab_style(
    style = cell_text(color = "darkgrey", weight = "bold"),
    locations = cells_stub()
  )
year trim mpg_city mpg_hwy
Ferrari
458 Speciale 2015 Base Coupe 13 17
458 Spider 2015 Base 13 17
458 Italia 2014 Base Coupe 13 17
488 GTB 2016 Base Coupe 15 22
California 2015 Base Convertible 16 23
GTC4Lusso 2017 Base Coupe 12 17
FF 2015 Base Coupe 11 16
F12Berlinetta 2015 Base Coupe 11 16
LaFerrari 2015 Base Coupe 12 16
Porsche
718 Boxster 2017 Base Convertible 21 28
718 Cayman 2017 Base Coupe 20 29
911 2016 Carrera Coupe 20 28
Panamera 2016 Base Sedan 18 28

Group summary

To affect the grouped summary rows (or grand summary rows) you can use cells_summary() or cells_grand_summary().

gtcars %>% 
  dplyr::filter(mfr %in% c("Ferrari", "Porsche", "Lamborghini")) %>% 
  dplyr::group_by(mfr) %>% 
  dplyr::slice_head(n = 3) %>% 
  dplyr::ungroup() %>% 
  dplyr::select(mfr, model:trim, mpg_city = mpg_c, mpg_hwy = mpg_h) %>%  
  gt(rowname_col = "model", groupname_col = "mfr") %>% 
  gt::summary_rows(
    groups = TRUE, columns = vars(mpg_city, mpg_hwy),
    fns = list(Average = ~mean(.)),
    formatter = fmt_number, decimals = 1
  ) %>% 
  tab_style(
    style = list(
      cell_text(color = "white", font = google_font("Fira Mono")),
      cell_fill("black")
    ),
    locations = cells_summary()
  )
year trim mpg_city mpg_hwy
Ferrari
458 Speciale 2015 Base Coupe 13 17
458 Spider 2015 Base 13 17
458 Italia 2014 Base Coupe 13 17
Average 13.0 17.0
Lamborghini
Aventador 2015 LP 700-4 Coupe 11 18
Huracan 2015 LP 610-4 Coupe 16 20
Gallardo 2014 LP 550-2 Coupe 12 20
Average 13.0 19.3
Porsche
718 Boxster 2017 Base Convertible 21 28
718 Cayman 2017 Base Coupe 20 29
911 2016 Carrera Coupe 20 28
Average 20.3 28.3

Spanners and labels

You can also affect the column labels or spanners above the labels. Note the use of a spanner id to make it easy to identify the specific spanner to apply the changes to.

exibble %>%
  dplyr::select(-fctr, -currency, -group) %>%
  gt(rowname_col = "row") %>%
  tab_spanner(
    label = "dates and times",
    id = "dt",
    columns = vars(date, time, datetime)
  ) %>%
  tab_style(
    style = cell_text(color = "darkgrey", transform = "uppercase"),
    locations = cells_column_spanners(spanners = "dt")
  ) %>% 
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels(columns = vars(date, time, datetime))
  )
num char dates and times
date time datetime
row_1 1.111e-01 apricot 2015-01-15 13:35 2018-01-01 02:22
row_2 2.222e+00 banana 2015-02-15 14:40 2018-02-02 14:33
row_3 3.333e+01 coconut 2015-03-15 15:45 2018-03-03 03:44
row_4 4.444e+02 durian 2015-04-15 16:50 2018-04-04 15:55
row_5 5.550e+03 NA 2015-05-15 17:55 2018-05-05 04:00
row_6 NA fig 2015-06-15 NA 2018-06-06 16:11
row_7 7.770e+05 grapefruit NA 19:10 2018-07-07 05:22
row_8 8.880e+06 honeydew 2015-08-15 20:20 NA

Add notes

You can also add footnotes or sourcenotes to arbitrary locations within the table. Both will “output” to the bottom of the table, but can place their respective indicators elsewhere.

Add footnote

Footnotes can be added to arbitrary locations with tab_footnote(). Here we add a footnote specifically to the mpg_h column label.

gtcars %>%
  dplyr::select(model, year, trq, mpg_h) %>%
  head(6) %>% 
  gt(rowname_col = "model") %>%
  tab_footnote(
    locations = cells_column_labels(vars(mpg_h)),
    footnote = "Miles per Gallon on Highway"
  )
year trq mpg_h1
GT 2017 550 18
458 Speciale 2015 398 17
458 Spider 2015 398 17
458 Italia 2014 398 17
488 GTB 2016 561 22
California 2015 557 23

1 Miles per Gallon on Highway

The location argument allows for other areas to be specified, and the footnote argument can also parse markdown/HTML with the md() and html() helpers.

gtcars %>%
  dplyr::select(model, year, trq, mpg_h) %>%
  head(6) %>% 
  gt(rowname_col = "model") %>%
  tab_footnote(
    locations = cells_stub(rows = c(2,3,6)),
    footnote = md("Manufacturing was interruped for these cars in **2015**")
  )
year trq mpg_h
GT 2017 550 18
458 Speciale1 2015 398 17
458 Spider1 2015 398 17
458 Italia 2014 398 17
488 GTB 2016 561 22
California1 2015 557 23

1 Manufacturing was interruped for these cars in 2015

Add a source note

Source notes, like “data sourced from…” can be added with tab_source_note(), and again it can parse arbitrary HTML or md.

source_tag <- "Data from <a href='https://www.edmunds.com'>Edmunds.com</a>"

gtcars %>%
  dplyr::select(model, year, trq, mpg_h) %>%
  head(6) %>% 
  gt(rowname_col = "model") %>% 
  tab_source_note(html(source_tag))
year trq mpg_h
GT 2017 550 18
458 Speciale 2015 398 17
458 Spider 2015 398 17
458 Italia 2014 398 17
488 GTB 2016 561 22
California 2015 557 23
Data from Edmunds.com

Conditional styling

You can transform specific portions of the table based on conditional logic.

Example conditionals include:

base:ifelse()
dplyr::if_else()
dplyr::case_when()

Text transform

You can change specific text based on a function with text_transform(). This is extremely powerful, but specific to only the column being transformed.

data.frame(
  count = c(1L, 2L, 3L, 4L, 5L),
  weight_g = c(150.65, 149.65, 171.28, 142.58, 139.04),
  color = c("green", "yellow", "yellow", "green", "yellow")
) %>% 
  gt() %>% 
  text_transform(
    locations = cells_body(
      columns = vars(weight_g)),
    fn = function(x) {
      paste0(
        x, " (",
        dplyr::case_when(
          x > 150   ~ "large",
          x <= 150  ~ "small"),
        ")")
    }
  ) 
count weight_g color
1 150.65 (large) green
2 149.65 (small) yellow
3 171.28 (large) yellow
4 142.58 (small) green
5 139.04 (small) yellow

Formatting changes

You can logically match to rows and apply specific styling to them such as color.

stocks <- data.frame(
  Symbol = c("GOOG", "FB", "AMZN", "NFLX", "TSLA"),
  Price = c(1265.13, 187.89, 1761.33, 276.82, 328.13),
  Change = c(4.14, 1.51, -19.45, 5.32, -12.45)
)
stocks %>% 
  gt() %>% 
  tab_style(
    style = cell_text(color = "red", weight = "bold"),
    locations = cells_body(
      columns = vars(Change),
      rows = Change < 0
    )
  ) %>% 
  tab_style(
    style = cell_text(color = "blue", weight = "bold"),
    locations = cells_body(
      columns = vars(Change),
      rows = Change >= 0
    )
  )
Symbol Price Change
GOOG 1265.13 4.14
FB 187.89 1.51
AMZN 1761.33 -19.45
NFLX 276.82 5.32
TSLA 328.13 -12.45

Style the table

With the tab_style() function we can target specific cells and apply styles to them. This is best done in conjunction with the helper functions cell_text(), cell_fill(), and cell_borders().

At present this function is focused on the application of styles for HTML output only (as such, other output formats will ignore all tab_style() calls). Using the aforementioned helper functions, here are some of the styles we can apply:

  • the background color of the cell (cell_fill(): color)

  • the cell’s text color, font, and size (cell_text(): color, font, size)

  • the text style (cell_text(): style), enabling the use of italics or oblique text.

  • the text weight (cell_text(): weight), allowing the use of thin to bold text (the degree of choice is greater with variable fonts)

  • the alignment and indentation of text (cell_text(): align and indent)

  • the cell borders (cell_borders())

data.frame(
  count = c(1L, 2L, 3L, 4L, 5L),
  weight_g = c(150.65, 149.65, 171.28, 142.58, 139.04),
  color = c("green", "yellow", "yellow", "green", "yellow")
) %>% 
  gt() %>% 
  tab_style(
    style = list(
      cell_fill(color = "lightgrey"),
      "font-variant: small-caps;"
    ),
    locations = cells_body(columns = vars(color))
  ) %>% 
  tab_style(
    style = list(
      cell_text(color = "green")
    ),
    locations = cells_body(
      columns = vars(color),
      # conditional logic
      rows = color == "green"
    )
  ) %>% 
  tab_style(
    style = list(
      cell_text(color = "goldenrod")
    ),
    locations = cells_body(
      columns = vars(color),
      # conditional logic
      rows = color == "yellow"
    )
  ) %>% 
  tab_style(
    style = list(
      cell_borders(sides = "right", color = "black", weight = px(3))
    ),
    locations = cells_body(
      # entire column
      columns = vars(weight_g)
    )
  ) %>% 
  tab_style(
    style = list(
      cell_text(transform = "uppercase", weight = "bold")
    ),
    # different location
    locations = cells_column_labels(everything())
  )
count weight_g color
1 150.65 green
2 149.65 yellow
3 171.28 yellow
4 142.58 green
5 139.04 yellow

Modify Columns

Column labels

The cols_label() function provides the flexibility to relabel one or more columns and we even have the option to use the md() or html() helper functions for rendering column labels from Markdown or using HTML.

head(mtcars) %>% 
  gt() %>% 
  cols_label(
    mpg = "Miles/Gal",
    cyl = "Cylinders"
  )
Miles/Gal Cylinders disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Relabel with markdown or HTML

You can also parse markdown with md() or HTML with html() within the label string.

head(mtcars) %>% 
  gt() %>% 
  cols_label(
    mpg = md("**Miles/Gal**"),       # recognizes markdown syntax
    cyl = html("<em>Cylinders</em>") # recognizes HTML syntax
  )
Miles/Gal Cylinders disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Column Alignment

The individual alignments of columns (which includes the column labels and all of their data cells) can be modified. We have the option to align text to the left, the center, and the right.

For this example we’ve also included all the table lines to “show” the alignment a bit better.

countrypops %>%
  dplyr::select(-contains("code")) %>%
  dplyr::filter(country_name == "Mongolia") %>%
  tail(5) %>%
  gt() %>%
  cols_align(
    align = "left",
    columns = vars(country_name)
  ) %>% 
  cols_align(
    align = "center",
    columns = vars(year)
  ) %>% 
  cols_align(
    align = "right",
    columns = vars(population)
  ) %>% 
  tab_options(table.width = px(300)) %>% 
  opt_table_lines()
country_name year population
Mongolia 2013 2869107
Mongolia 2014 2923896
Mongolia 2015 2976877
Mongolia 2016 3027398
Mongolia 2017 3075647

Optimal alignment

Typically, the best practice is to use left-align for text of variable length and right-align for numeric values. The reasoning can be highlighted in the table below. We want to align numeric values on the same scale so that they can be compared on the same scale, whereas text is more easily readable left-aligned. Center-align can be used with strings or values of equal-length.

Note that fmt_number and other fmt_??? applied to numeric will automatically right-align, but text will default to left-align.

gt::exibble %>% 
  select(group, char, num, currency) %>% 
  gt() %>% 
  cols_align(align = "center", columns = vars(group)) %>% 
  fmt_number(columns = vars(num)) %>% 
  fmt_currency(columns = vars(currency))
group char num currency
grp_a apricot 0.11 $49.95
grp_a banana 2.22 $17.95
grp_a coconut 33.33 $1.39
grp_a durian 444.40 $65,100.00
grp_b NA 5,550.00 $1,325.81
grp_b fig NA $13.26
grp_b grapefruit 777,000.00 NA
grp_b honeydew 8,880,000.00 $0.44

Column Width

We choose which columns get specific widths. This can be in units of pixels (easily set by use of the px() helper function), or, as percentages (where the pct() helper function is useful).

countrypops %>%
  dplyr::select(-contains("code")) %>%
  dplyr::filter(country_name == "Mongolia") %>%
  tail(5) %>%
  gt() %>% 
  cols_width(
    vars(country_name) ~ px(200),
    vars(year) ~ px(50),
    vars(population) ~ px(100)
  )
country_name year population
Mongolia 2013 2869107
Mongolia 2014 2923896
Mongolia 2015 2976877
Mongolia 2016 3027398
Mongolia 2017 3075647

Change all columns

You can use the everything() function to affect all columns (or remaining columns).

mtcars %>% 
  tibble::rownames_to_column("names") %>% 
  head(8) %>%
  gt() %>% 
  cols_width(
    vars(names) ~ px(150),
    everything() ~ px(60)
  ) %>% 
  opt_table_lines()
names mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1
Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4
Merc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2

Move columns

You can move columns to the beginning, end, or arbitrary locations.

To start

countrypops %>% 
  dplyr::select(country_name, year:population) %>% 
  tail(8) %>% 
  gt() %>% 
  cols_move_to_start(vars(year))
year country_name population
2010 Zimbabwe 14086317
2011 Zimbabwe 14386649
2012 Zimbabwe 14710826
2013 Zimbabwe 15054506
2014 Zimbabwe 15411675
2015 Zimbabwe 15777451
2016 Zimbabwe 16150362
2017 Zimbabwe 16529904

To end

countrypops %>% 
  dplyr::select(country_name, year:population) %>% 
  tail(8) %>% 
  gt() %>% 
  cols_move_to_end(vars(year))
country_name population year
Zimbabwe 14086317 2010
Zimbabwe 14386649 2011
Zimbabwe 14710826 2012
Zimbabwe 15054506 2013
Zimbabwe 15411675 2014
Zimbabwe 15777451 2015
Zimbabwe 16150362 2016
Zimbabwe 16529904 2017

Wherever you want

countrypops %>% 
  dplyr::select(country_name, year:population) %>% 
  tail(8) %>% 
  gt() %>% 
  cols_move(
    columns = vars(country_name),
    after = vars(year)
    )
year country_name population
2010 Zimbabwe 14086317
2011 Zimbabwe 14386649
2012 Zimbabwe 14710826
2013 Zimbabwe 15054506
2014 Zimbabwe 15411675
2015 Zimbabwe 15777451
2016 Zimbabwe 16150362
2017 Zimbabwe 16529904

Hide columns

You can also hide arbitrary columns, but still reference them inside gt.

zim_code <- unique(countrypops$country_code_2) %>% .[length(.)]
countrypops %>% 
  tail(8) %>% 
  gt() %>% 
  cols_hide(columns = dplyr::contains("code")) %>% 
  tab_footnote(
    footnote = paste("The country code is", zim_code),
    locations = cells_body(
      columns = vars(country_name),
      rows = country_code_2 == zim_code
    )
  )
country_name year population
Zimbabwe1 2010 14086317
Zimbabwe1 2011 14386649
Zimbabwe1 2012 14710826
Zimbabwe1 2013 15054506
Zimbabwe1 2014 15411675
Zimbabwe1 2015 15777451
Zimbabwe1 2016 16150362
Zimbabwe1 2017 16529904

1 The country code is ZW

Merge columns

Columns can be merged with glue-like syntax.

sp500 %>%
  dplyr::slice(50:55) %>%
  dplyr::select(-volume, -adj_close) %>%
  gt() %>%
  cols_merge(
    columns = vars(open, close),
    hide_columns = vars(close),
    pattern = "{1}&mdash;{2}"
  ) %>%
  cols_merge(
    columns = vars(low, high),
    hide_columns = vars(high),
    pattern = "{1}&mdash;{2}"
  ) %>%
  cols_label(
    open = "open/close",
    low = "low/high"
  )
date open/close low/high
2015-10-21 2033.47—2018.94 2017.22—2037.97
2015-10-20 2033.13—2030.77 2026.61—2039.12
2015-10-19 2031.73—2033.66 2022.31—2034.45
2015-10-16 2024.37—2033.11 2020.46—2033.54
2015-10-15 1996.47—2023.86 1996.47—2024.15
2015-10-14 2003.66—1994.24 1990.73—2009.56

Table Customization

You can customize table “theme” using several options, which can all be combined:

Bordered

head(mtcars) %>% 
  gt() %>% 
  opt_table_lines("all")
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Borderless

head(mtcars) %>% 
  gt() %>% 
  opt_table_lines("none")
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Outlined

head(mtcars) %>% 
  gt() %>% 
  opt_table_outline()
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Striped

head(mtcars) %>% 
  gt() %>% 
  opt_row_striping()
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Add dividers

You can specify locations to add borders/dividers and control the weight/color/side of the border.

head(mtcars) %>% 
  gt() %>% 
  tab_style(
    style = cell_borders(sides = "right", color = "black", 
                         style = "dashed", weight = px(3)),
    locations = cells_body(
      columns = vars(cyl)
    )
  ) %>% 
  tab_style(
    style = cell_borders(sides = "bottom", color = "black", weight = px(3)),
    locations = cells_column_labels(everything())
  )
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

You can also include locations outside of the cell body, ie the column labels or other locations.

head(mtcars) %>% 
  dplyr::select(cyl, everything()) %>% 
  gt() %>% 
  opt_table_lines("none") %>% 
  opt_row_striping() %>% 
  tab_style(
    style = cell_borders(sides = "right", color = "black", weight = px(3)),
    locations = cells_body(
      columns = vars(cyl)
    )
  ) %>% 
  tab_style(
    style = cell_borders(sides = c("top", "bottom"), 
                         color = "black", weight = px(3)),
    locations = cells_column_labels(everything())
  ) %>% 
  tab_style(
    style = cell_borders(sides = "bottom", color = "black", weight = px(3)),
    locations = cells_body(rows = 6)
  )
cyl mpg disp hp drat wt qsec vs am gear carb
6 21.0 160 110 3.90 2.620 16.46 0 1 4 4
6 21.0 160 110 3.90 2.875 17.02 0 1 4 4
4 22.8 108 93 3.85 2.320 18.61 1 1 4 1
6 21.4 258 110 3.08 3.215 19.44 1 0 3 1
8 18.7 360 175 3.15 3.440 17.02 0 0 3 2
6 18.1 225 105 2.76 3.460 20.22 1 0 3 1

Adjust fonts

You can use system fonts or bring in Google fonts with google_font().

# change font for entire table
head(mtcars) %>% 
  dplyr::select(cyl, everything()) %>% 
  gt() %>% 
  opt_table_font(font = google_font("Fira Mono"))
cyl mpg disp hp drat wt qsec vs am gear carb
6 21.0 160 110 3.90 2.620 16.46 0 1 4 4
6 21.0 160 110 3.90 2.875 17.02 0 1 4 4
4 22.8 108 93 3.85 2.320 18.61 1 1 4 1
6 21.4 258 110 3.08 3.215 19.44 1 0 3 1
8 18.7 360 175 3.15 3.440 17.02 0 0 3 2
6 18.1 225 105 2.76 3.460 20.22 1 0 3 1

Fonts by location

Adjusting font by location can be done via tab_style().

head(mtcars) %>% 
  dplyr::select(cyl, everything()) %>% 
  gt() %>% 
  # change cell body font
  tab_style(
    style = cell_text(
      font = google_font("Fira Mono"), size = px(14)),
    locations = cells_body(columns = everything())
  ) %>% 
  # change column labels
  tab_style(
    style = cell_text(
      font = google_font("Indie Flower"), 
      weight = "bold",
      size = px(30)
      ),
    locations = cells_column_labels(everything())
  )
cyl mpg disp hp drat wt qsec vs am gear carb
6 21.0 160 110 3.90 2.620 16.46 0 1 4 4
6 21.0 160 110 3.90 2.875 17.02 0 1 4 4
4 22.8 108 93 3.85 2.320 18.61 1 1 4 1
6 21.4 258 110 3.08 3.215 19.44 1 0 3 1
8 18.7 360 175 3.15 3.440 17.02 0 0 3 2
6 18.1 225 105 2.76 3.460 20.22 1 0 3 1

And the changes can be made outside of just the body of the table, for example the title/header.

countrypops %>%
  dplyr::select(-contains("code")) %>%
  tail(5) %>%
  gt() %>% 
  tab_style(
    style = cell_text(font = google_font("Fira Mono")),
    locations = cells_body(columns = vars(year, population))
  ) %>% 
  tab_style(
    style = cell_text(font = google_font("Raleway"), weight = "bold"),
    locations = cells_body(columns = vars(country_name))
  ) %>% 
  tab_style(
    style = cell_text(
      font = google_font("Indie Flower"), 
      weight = "bold", 
      align = "left",
      size = px(40)
      ),
    locations = cells_title("title")
  ) %>% 
  tab_header("Population changes")
Population changes
country_name year population
Zimbabwe 2013 15054506
Zimbabwe 2014 15411675
Zimbabwe 2015 15777451
Zimbabwe 2016 16150362
Zimbabwe 2017 16529904

Table options

Modify the options available in a table. These options are named by the components, the subcomponents, and the element that can adjusted.

This is where the bulk of theme-changes can be done. tab_options has dozens of different table components that can be adjusted. The full details can be found in the {gt} documentation. You can customize all sorts of arbitrary components based globally.

head(mtcars) %>% 
  gt() %>% 
  tab_options(
    table.background.color = "black",
    column_labels.background.color = "grey",
    column_labels.font.size = px(16),
    table.font.size = px(12),
    data_row.padding = px(4),
    table.width = px(250)
  )
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Create a theme

Creating a theme can be done by passing in a gt object, and setting some parameters in various gt functions. Here we define a basic theme.

my_gt_theme <- function(data, ...) {
  data %>%
    tab_options(
      table.background.color = "black",
      column_labels.background.color = "grey",
      column_labels.font.size = px(16),
      table.font.size = px(12),
      data_row.padding = px(4),
      ...
    )
}

And we can then apply that theme. Note that the theme is intentionally relative garish but we can see that we turned some of the arguments into a one-liner.

head(gtcars) %>% 
  gt() %>% 
  my_gt_theme(table.font.color.light = "lightgreen")
mfr model year trim bdy_style hp hp_rpm trq trq_rpm mpg_c mpg_h drivetrain trsmn ctry_origin msrp
Ford GT 2017 Base Coupe coupe 647 6250 550 5900 11 18 rwd 7a United States 447000
Ferrari 458 Speciale 2015 Base Coupe coupe 597 9000 398 6000 13 17 rwd 7a Italy 291744
Ferrari 458 Spider 2015 Base convertible 562 9000 398 6000 13 17 rwd 7a Italy 263553
Ferrari 458 Italia 2014 Base Coupe coupe 562 9000 398 6000 13 17 rwd 7a Italy 233509
Ferrari 488 GTB 2016 Base Coupe coupe 661 8000 561 3000 15 22 rwd 7a Italy 245400
Ferrari California 2015 Base Convertible convertible 553 7500 557 4750 16 23 rwd 7a Italy 198973

Example Theme

A “prettier” theme based off an ESPN table style.

gt_theme_espn <- function(data, ...){
  data %>% 
    opt_all_caps()  %>%
    opt_table_font(
      font = list(
        google_font("Lato"),
        default_fonts()
      )
    )  %>% 
    opt_row_striping() %>% 
    tab_options(
      row.striping.background_color = "#fafafa",
      table_body.hlines.color = "#f6f7f7",
      source_notes.font.size = 12,
      table.font.size = 16,
      table.width = px(700),
      heading.align = "left",
      heading.title.font.size = 24,
      table.border.top.color = "transparent",
      table.border.top.width = px(3),
      data_row.padding = px(7),
      ...
    ) 
}
head(gtcars) %>% 
  dplyr::select(mfr:mpg_c) %>% 
  gt() %>% 
  gt_theme_espn()
mfr model year trim bdy_style hp hp_rpm trq trq_rpm mpg_c
Ford GT 2017 Base Coupe coupe 647 6250 550 5900 11
Ferrari 458 Speciale 2015 Base Coupe coupe 597 9000 398 6000 13
Ferrari 458 Spider 2015 Base convertible 562 9000 398 6000 13
Ferrari 458 Italia 2014 Base Coupe coupe 562 9000 398 6000 13
Ferrari 488 GTB 2016 Base Coupe coupe 661 8000 561 3000 15
Ferrari California 2015 Base Convertible convertible 553 7500 557 4750 16

Custom CSS

For more control over styling, you can add custom class names to the table and apply your own CSS. Note that this can require more effort than the built in gt functions, but also allows some things that aren’t possible by the functions align (like hover highlighting!).

 exibble %>%
  dplyr::select(num, currency) %>%
  gt(id = "one") %>% # need to name the table so that you can apply CSS
  fmt_currency(
    columns = vars(currency),
    currency = "HKD"
  ) %>%
  fmt_scientific(
    columns = vars(num)
  ) %>%
  opt_css(
    css = "
    #one .gt_table {
      background-color: lightgrey;
    }
    #one .gt_row {
      padding: 20px 30px;
    }
    #one tr:hover {
    background-color: #f5f8ff;
    }
    #one .gt_col_heading {
      text-align: center !important;
    }
    "
  )
num currency
1.11 × 10−1 HK$49.95
2.22 HK$17.95
3.33 × 101 HK$1.39
4.44 × 102 HK$65,100.00
5.55 × 103 HK$1,325.81
NA HK$13.26
7.77 × 105 NA
8.88 × 106 HK$0.44

The examples here embed CSS for demonstration, but it’s often better to put CSS in an external style sheet. You can learn more about adding custom CSS to R Markdown documents here, or to Shiny apps here.

Embed URLs

You can also use things like htmltools or glue to arbitrarily build HTML content like hyperlinks.

ex_sites <- data.frame(
  Address = c("https://google.com", "https://yahoo.com", "https://duckduckgo.com"),
  Site = c("Google", "Yahoo", "DuckDuckGo")
)
gt(ex_sites) %>% 
  text_transform(
    locations = cells_body(columns = vars(Address)),
    fn = function(x) {
    purrr::map(x,  ~htmltools::tags$a(href = .x, target = "_blank", .x))
      }
  ) %>% 
  text_transform(
    locations = cells_body(columns = vars(Site)),
    fn = function(x) {
    purrr::map2(
      .x = x, .y = ex_sites$Address, 
      .f = ~glue::glue('<a href="{.y}" target="_blank">{.x}</a>'))
      }
  )

Conditional formatting

Color scales

To add color scales, you can use R’s built-in color utilities (or other color manipulation packages like {paletteer}):

Conditional coloring

It’s possible to add color to data cells according to their values. The data_color() function colors all rows of any columns supplied. There are two ways to define how cells are colored: (1) through the use of a supplied color palette, and (2) through use of a color mapping function available from the {scales} package. The first method colorizes cell data according to whether values are character or numeric. The second method provides more control over how cells are colored since we provide an explicit color function and thus other requirements such as bin counts, cut points, or a numeric domain.

countrypops %>%
  dplyr::filter(country_name == "Mongolia") %>%
  dplyr::select(-contains("code")) %>%
  tail(10) %>%
  gt() %>%
  data_color(
    columns = vars(population),
    colors = scales::col_numeric(
      palette = c(
        "white", "orange", "red"),
      domain = NULL)
  )
country_name year population
Mongolia 2008 2628131
Mongolia 2009 2668289
Mongolia 2010 2712650
Mongolia 2011 2761516
Mongolia 2012 2814226
Mongolia 2013 2869107
Mongolia 2014 2923896
Mongolia 2015 2976877
Mongolia 2016 3027398
Mongolia 2017 3075647


Multiple columns

This can also be applied across multiple columns at once. Here’s an example using the built in nottem dataset. While red-green color scales are very commonly used, they are not color-blind friendly.

We can alternatively use something like red-white-blue, or purple-white-green.

dimnames <- list(start(nottem)[1]:end(nottem)[1], month.abb)
temps <- matrix(nottem, ncol = 12, byrow = TRUE, dimnames = dimnames) %>% 
  data.frame() %>% 
  tibble::rownames_to_column() %>% 
  head(10)

temps %>% 
  gt() %>% 
  data_color(
    columns = vars(month.abb),
    colors = scales::col_numeric(
      c("#63be7b", "#ffeb84", "#f87274"), 
      domain = range(nottem))
  )
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
1920 40.6 40.8 44.4 46.7 54.1 58.5 57.7 56.4 54.3 50.5 42.9 39.8
1921 44.2 39.8 45.1 47.0 54.1 58.7 66.3 59.9 57.0 54.2 39.7 42.8
1922 37.5 38.7 39.5 42.1 55.7 57.8 56.8 54.3 54.3 47.1 41.8 41.7
1923 41.8 40.1 42.9 45.8 49.2 52.7 64.2 59.6 54.4 49.2 36.3 37.6
1924 39.3 37.5 38.3 45.5 53.2 57.7 60.8 58.2 56.4 49.8 44.4 43.6
1925 40.0 40.5 40.8 45.1 53.8 59.4 63.5 61.0 53.0 50.0 38.1 36.3
1926 39.2 43.4 43.4 48.9 50.6 56.8 62.5 62.0 57.5 46.7 41.6 39.8
1927 39.4 38.5 45.3 47.1 51.7 55.0 60.4 60.5 54.7 50.3 42.3 35.2
1928 40.8 41.1 42.8 47.3 50.9 56.4 62.2 60.5 55.4 50.2 43.0 37.3
1929 34.8 31.3 41.0 43.9 53.1 56.9 62.5 60.3 59.8 49.2 42.9 41.9

“Hulk” Colors

temps %>% 
  gt() %>% 
  data_color(
    columns = vars(month.abb),
    colors = scales::col_numeric(
      colorspace::diverge_hcl(n = 9, palette = "Purple-Green") %>% rev(), 
      domain = range(nottem))
  )
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
1920 40.6 40.8 44.4 46.7 54.1 58.5 57.7 56.4 54.3 50.5 42.9 39.8
1921 44.2 39.8 45.1 47.0 54.1 58.7 66.3 59.9 57.0 54.2 39.7 42.8
1922 37.5 38.7 39.5 42.1 55.7 57.8 56.8 54.3 54.3 47.1 41.8 41.7
1923 41.8 40.1 42.9 45.8 49.2 52.7 64.2 59.6 54.4 49.2 36.3 37.6
1924 39.3 37.5 38.3 45.5 53.2 57.7 60.8 58.2 56.4 49.8 44.4 43.6
1925 40.0 40.5 40.8 45.1 53.8 59.4 63.5 61.0 53.0 50.0 38.1 36.3
1926 39.2 43.4 43.4 48.9 50.6 56.8 62.5 62.0 57.5 46.7 41.6 39.8
1927 39.4 38.5 45.3 47.1 51.7 55.0 60.4 60.5 54.7 50.3 42.3 35.2
1928 40.8 41.1 42.8 47.3 50.9 56.4 62.2 60.5 55.4 50.2 43.0 37.3
1929 34.8 31.3 41.0 43.9 53.1 56.9 62.5 60.3 59.8 49.2 42.9 41.9

Red blue palette

temps %>% 
  gt() %>% 
  data_color(
    columns = vars(month.abb),
    colors = scales::col_numeric(
      colorspace::diverge_hcl(n = 9, palette = "Blue-Red 3"), 
      domain = range(nottem))
  )
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
1920 40.6 40.8 44.4 46.7 54.1 58.5 57.7 56.4 54.3 50.5 42.9 39.8
1921 44.2 39.8 45.1 47.0 54.1 58.7 66.3 59.9 57.0 54.2 39.7 42.8
1922 37.5 38.7 39.5 42.1 55.7 57.8 56.8 54.3 54.3 47.1 41.8 41.7
1923 41.8 40.1 42.9 45.8 49.2 52.7 64.2 59.6 54.4 49.2 36.3 37.6
1924 39.3 37.5 38.3 45.5 53.2 57.7 60.8 58.2 56.4 49.8 44.4 43.6
1925 40.0 40.5 40.8 45.1 53.8 59.4 63.5 61.0 53.0 50.0 38.1 36.3
1926 39.2 43.4 43.4 48.9 50.6 56.8 62.5 62.0 57.5 46.7 41.6 39.8
1927 39.4 38.5 45.3 47.1 51.7 55.0 60.4 60.5 54.7 50.3 42.3 35.2
1928 40.8 41.1 42.8 47.3 50.9 56.4 62.2 60.5 55.4 50.2 43.0 37.3
1929 34.8 31.3 41.0 43.9 53.1 56.9 62.5 60.3 59.8 49.2 42.9 41.9

Multiple colors

Multiple calls to data_color() can provide different color palettes or ranges.

gtcars %>% 
  dplyr::filter(mfr == "Ferrari", hp < 900) %>% 
  dplyr::select(model, hp, mpg_c, mpg_h, msrp) %>% 
  gt() %>% 
  data_color(
    columns = vars(hp),
    colors = scales::col_numeric(
      palette = c(
        "white", "orange", "red"),
      domain = c(500, 750))
  ) %>% 
  data_color(
    columns = vars(mpg_c, mpg_h),
    colors = scales::col_numeric(
      palette = c(
        "white", "green"),
      domain = c(10, 25))
  ) %>% 
  data_color(
    columns = vars(msrp),
    colors = scales::col_numeric(
      palette = c(
        "white", "pink", "red"),
      domain = NULL)
  )
model hp mpg_c mpg_h msrp
458 Speciale 597 13 17 291744
458 Spider 562 13 17 263553
458 Italia 562 13 17 233509
488 GTB 661 15 22 245400
California 553 16 23 198973
GTC4Lusso 680 12 17 298000
FF 652 11 16 295000
F12Berlinetta 731 11 16 319995

{paleteer} palettes

To make this process easier we can elect to use the {paletteer} package, which makes a wide range of palettes from various R packages readily available.

pizzaplace %>%
  dplyr::filter(
    type %in% c("chicken", "supreme")) %>%
  dplyr::group_by(type, size) %>%
  dplyr::summarize(
    sold = dplyr::n(),
    income = sum(price)
  ) %>%
  gt(rowname_col = "size") %>%
  data_color(
    columns = vars(sold, income),
    colors = scales::col_numeric(
      palette = paletteer::paletteer_d(
        palette = "ggsci::red_material"
        ) %>% as.character(),
      domain = NULL
      )
  )
## `summarise()` has grouped output by 'type'. You can override using the `.groups` argument.
sold income
chicken
L 4932 102339.0
M 3894 65224.5
S 2224 28356.0
supreme
L 4564 94258.5
M 4046 66475.0
S 3377 47463.5

Factors

Factors are typically more appropriate with qualitative palettes, and we can use scales::col_factor() to apply colors to the specific column of interest. Note that the color palette needs to be equal to the unique number of factors. In the example below we pass n = 3 since we have 3 different trim types.

gtcars %>% 
  dplyr::filter(mfr == "Ferrari", hp < 900) %>% 
  dplyr::select(model, hp, trim, mpg_h, msrp) %>% 
  gt() %>% 
  data_color(
    columns = vars(trim),
    colors = scales::col_factor(
      palette = paletteer::paletteer_d(
        n = 3, palette = "colorblindr::OkabeIto"
        ) %>% as.character(),
      domain = NULL
      )
  )
model hp trim mpg_h msrp
458 Speciale 597 Base Coupe 17 291744
458 Spider 562 Base 17 263553
458 Italia 562 Base Coupe 17 233509
488 GTB 661 Base Coupe 22 245400
California 553 Base Convertible 23 198973
GTC4Lusso 680 Base Coupe 17 298000
FF 652 Base Coupe 16 295000
F12Berlinetta 731 Base Coupe 16 319995

Parse arbitrary HTML

Because gt supports HTML, you can optionally “create” HTML strings prior to passing them into gt proper.

color_span <- function(x){paste0("<span style='color: ", x, ";'>", x, "</span>")}

data.frame(
  count = c(1L, 2L, 3L, 4L, 5L),
  weight_g = c(150.65, 149.65, 171.28, 142.58, 139.04),
  color = c("green", "yellow", "yellow", "green", "yellow")
) %>% 
  mutate(color = color_span(color)) %>% 
  mutate(color = purrr::map(color, gt::html)) %>% 
  gt() 
count weight_g color
1 150.65 green
2 149.65 yellow
3 171.28 yellow
4 142.58 green
5 139.04 yellow

Combine and stack

Combine text into div containers and then “stack” the text on top of each other with alternating color.

stack_function <- function(x){
  
  name <- sub(x = x, pattern = " .*$", replacement = "")
  model <- sub(x = x, pattern = ".*? ", replacement = "")

  
  glue::glue(
    "<div style='line-height:10px'>
    <span style='font-weight:bold;font-variant:small-caps;font-size:14px'>
    {name}</div>
    <div style='line-height:12px'>
    <span style ='font-weight:bold;color:grey;font-size:10px'>
    {model}</span></div>"
  )
    }

head(gtcars) %>% 
  dplyr::select(mfr, model, year, trim, hp) %>%
  gt() %>% 
  cols_merge(
    columns = vars(mfr, model)
  ) %>% 
  text_transform(
    locations = cells_body(
      columns = vars(mfr)
    ),
    fn = stack_function
  ) %>% 
  tab_options(
    data_row.padding = px(5),
  )
mfr year trim hp
Ford
GT
2017 Base Coupe 647
Ferrari
458 Speciale
2015 Base Coupe 597
Ferrari
458 Spider
2015 Base 562
Ferrari
458 Italia
2014 Base Coupe 562
Ferrari
488 GTB
2016 Base Coupe 661
Ferrari
California
2015 Base Convertible 553

Align symbol on first row only

We can align text on the first row only even with a suffix (ie symbol at the end). This can be done with just gt, but it’s a bit verbose.

This example applies a percent label to the hp_pct column and properly maintains the decimal place alignment.

head(gtcars) %>%
  mutate(hp_pct = (hp/max(hp) * 100)) %>% 
  dplyr::select(mfr, model, year, trim, hp, hp_pct) %>%
  gt() %>%
  # use a mono-spaced font
  tab_style(
    style = cell_text(font = google_font("Fira Mono")),
    locations = cells_body(columns = vars(hp_pct))
    ) %>% 
  # align the column of interst to right
  cols_align(align = "right", columns = vars(hp_pct)) %>% 
  # round and transform the first row to percent
  text_transform(
    locations = cells_body(vars(hp_pct), rows = 1),
    fn = function(x){ 
      fmt_val <- format(as.double(x), nsmall = 1, digits = 1)
      paste0(fmt_val, "%") %>% gt::html()}
  ) %>% 
  text_transform(
    locations = cells_body(vars(hp_pct), rows = 2:6),
    fn = function(x){ 
      # round remaining rows, add a non-breaking space
     fmt_val <- format(as.double(x), nsmall = 1, digits = 1)
     lapply(fmt_val, function(x) paste0(x, '&nbsp') %>% gt::html())
  })
mfr model year trim hp hp_pct
Ford GT 2017 Base Coupe 647 97.9%
Ferrari 458 Speciale 2015 Base Coupe 597 90.3 
Ferrari 458 Spider 2015 Base 562 85.0 
Ferrari 458 Italia 2014 Base Coupe 562 85.0 
Ferrari 488 GTB 2016 Base Coupe 661 100.0 
Ferrari California 2015 Base Convertible 553 83.7 

We can do the same thing with a custom gt function that we’ll call fmt_symbol_first().

fmt_symbol_first <- function(
  gt_data,
  column = NULL,        # column of interest to apply to
  symbol = NULL,        # symbol to add, optionally
  suffix = "",          # suffix to add, optionally
  decimals = NULL,      # number of decimal places to round to
  last_row_n,           # what's the last row in data?
  symbol_first = FALSE  # symbol before or after suffix?
) {
  
  # Test and error out if mandatory columns are missing
  stopifnot("`symbol_first` argument must be a logical" = is.logical(symbol_first))
  stopifnot("`last_row_n` argument must be specified and numeric" = is.numeric(last_row_n))
  stopifnot("Input must be a gt table" = class(gt_data)[[1]] == "gt_tbl")

  # needs to type convert to double to play nicely with decimals and rounding
  # as it's converted to character by gt::text_transform
  add_to_first <- function(x, suff = suffix, symb = symbol) {
    if (!is.null(decimals)) {
      x <- suppressWarnings(as.double(x))
      fmt_val <- format(x = x, nsmall = decimals, digits = decimals)
    } else {
      fmt_val <- x
    }

    # combine the value, passed suffix, symbol -> html
    if (isTRUE(symbol_first)) {
      paste0(fmt_val, symb, suff) %>% gt::html()
    } else {
      paste0(fmt_val, suff, symb) %>% gt::html()
    }
  }

  # repeat non-breaking space for combined length of suffix + symbol
  # logic is based on is a NULL passed or not
  if (!is.null(symbol) | !identical(as.character(symbol), character(0))) {
    suffix <- ifelse(identical(as.character(suffix), character(0)), "", suffix)
    length_nbsp <- c("&nbsp", rep("&nbsp", nchar(suffix))) %>%
      paste0(collapse = "")
  } else {
    suffix <- ifelse(identical(as.character(suffix), character(0)), "", suffix)
    length_nbsp <- rep("&nbsp", nchar(suffix)) %>%
      paste0(collapse = "")
  }

  # affect rows OTHER than the first row
  add_to_remainder <- function(x, length = length_nbsp) {
    if (!is.null(decimals)) {
      # if decimal not null, convert to double
      x <- suppressWarnings(as.double(x))
      # then round and format ALL to force specific decimals
      fmt_val <- format(x = x, nsmall = decimals, digits = decimals)
    } else {
      fmt_val <- x
    }
    paste0(fmt_val, length) %>% lapply(FUN = gt::html)
  }

  # pass gt object
  # align right to make sure the spacing is meaningful
  gt_data %>%
    cols_align(align = "right", columns = vars({{ column }})) %>%
    # convert to mono-font for column of interest
    tab_style(
      style = cell_text(font = google_font("Fira Mono")),
      locations = cells_body(columns = vars({{ column }}))
    ) %>%
    # transform first rows
    text_transform(
      locations = cells_body(vars({{ column }}), rows = 1),
      fn = add_to_first
    ) %>%
    # transform remaining rows
    text_transform(
      locations = cells_body(vars({{ column }}), rows = 2:last_row_n),
      fn = add_to_remainder
    )
}

Apply the custom function.

We can then use the function as a one-liner, and format just that column of interest.

head(gtcars) %>%
  mutate(hp_pct = (hp/max(hp) * 100)) %>% 
  dplyr::select(mfr, model, year, trim, hp, hp_pct) %>%
  gt() %>% 
  opt_table_lines() %>% 
  fmt_symbol_first(column = hp_pct, decimals = 1, suffix = "%", last_row_n = 6)
mfr model year trim hp hp_pct
Ford GT 2017 Base Coupe 647 97.9%
Ferrari 458 Speciale 2015 Base Coupe 597 90.3 
Ferrari 458 Spider 2015 Base 562 85.0 
Ferrari 458 Italia 2014 Base Coupe 562 85.0 
Ferrari 488 GTB 2016 Base Coupe 661 100.0 
Ferrari California 2015 Base Convertible 553 83.7 

Sparkline plots

We can embed sparkline plots with some help from the kableExtra package.

mtcars %>%
  group_by(cyl) %>%
  summarize(mpg_data = list(mpg), .groups = "drop") %>%
  gt() %>%
  text_transform(
    locations = cells_body(columns = vars(mpg_data)),
    fn = function(x) {
      data_in <- purrr::pluck(., "_data", "mpg_data")
      plot <- purrr::map(
        data_in, ~ kableExtra::spec_plot(
          .x, ylim = range(mtcars$mpg), 
          same_lim = TRUE, width = 300, height = 70
          )
        )
      
      plot <- purrr::map_chr(plot, "svg_text")
    }
  )
cyl mpg_data
4
6
8

Create a function

We can alternatively write a function to do something similar.

gt_plot <- function(table_data, plot_col, data_col, plot_fun, ...){
  # save the data extract ahead of time 
  # to be used in our anonymous function below
  data_in = pluck(table_data, "_data", data_col)

  text_transform(
    table_data,
    # note the use of {{}} here - this is tidy eval
    # that allows you to indicate specific columns
    locations = cells_body(columns = vars({{plot_col}})),
    fn = function(x){
      plot <- purrr::map(data_in, plot_fun, width = 300, height = 70, same_lim = FALSE, ...)
      plot_svg <- purrr::map(plot, "svg_text")
      purrr::map(plot_svg, gt::html)
    }
  )
}

And then we can use that function!

mtcars %>% 
  group_by(cyl) %>% 
  summarize(mpg_data = list(mpg), .groups = "drop") %>% 
  gt() %>% 
  # note you can leave mpg_data unquoted for the tidyeval
  # but have to quote mpg_data for the pluck
  gt_plot(mpg_data, "mpg_data", plot_fun = kableExtra::spec_plot)
cyl mpg_data
4
6
8

Interactive sparklines

We can use the sparkline package to embed interactive sparklines.

gt_spark <- function(table_data, plot_col, data_col){
  # save the data extract ahead of time 
  # to be used in our anonymous function below
  data_in = purrr::pluck(table_data, "_data", data_col)
  
  text_transform(
    table_data,
    # note the use of {{}} here - this is tidy eval
    # that allows you to indicate specific columns
    locations = cells_body(columns = vars({{plot_col}})),
    fn = function(x){
      sparkline_plot <- purrr::map(
        data_in, 
        ~sparkline::spk_chr(values = .x, chartRangeMin = 0)
        )
      
      purrr::map(sparkline_plot, gt::html)
    }
  )
}

We can then apply the function to work very succinctly, referencing only the internal list-column data.

mtcars %>% 
  group_by(cyl) %>% 
  summarize(mpg_data = list(mpg), .groups = "drop") %>% 
  gt() %>% 
  # note you can leave mpg_data unquoted for the tidyeval
  # but have to quote mpg_data for the pluck
  gt_spark(mpg_data, "mpg_data")
cyl mpg_data
4
6
8

Tooltips

Tooltips can be added with HTML tags.

library(htmltools)

#    
# Add tooltip to column labels
with_tooltip <- function(value, tooltip) {
  tags$abbr(
    style = "text-decoration: underline;
    text-decoration-style: solid; color: blue",
    title = tooltip,
    value
  ) %>% 
    as.character()
}

mtcars %>% 
  head() %>% 
  tibble::rownames_to_column() %>% 
  select(rowname, mpg:hp) %>% 
  gt() %>% 
   cols_label(
    mpg = gt::html(with_tooltip("MPG", "Miles per Gallon")),
    cyl = gt::html(with_tooltip("CYL", "Number of Cylinders")),
    disp = gt::html(with_tooltip("DISP", "Displacement")),
    hp = gt::html(with_tooltip("HP", "Horsepower")),
  )
MPG CYL DISP HP
Mazda RX4 21.0 6 160 110
Mazda RX4 Wag 21.0 6 160 110
Datsun 710 22.8 4 108 93
Hornet 4 Drive 21.4 6 258 110
Hornet Sportabout 18.7 8 360 175
Valiant 18.1 6 225 105

Add icons

You can add arbitrary icons with the fontawesome R package.

mtcars %>% 
  head() %>% 
  gt() %>% 
  text_transform(
    locations = cells_body(columns = vars(cyl), rows = cyl == 4),
    fn = function(x){gt::html(fontawesome::fa("truck-pickup", fill = "blue"))}
  ) %>% 
  text_transform(
    locations = cells_body(columns = vars(cyl), rows = cyl == 6),
    fn = function(x){gt::html(fontawesome::fa("truck", fill = "grey"))}
  ) %>% 
  text_transform(
    locations = cells_body(columns = vars(cyl), rows = cyl == 8),
    fn = function(x){gt::html(fontawesome::fa("truck-monster", fill = "red"))}
  )
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 160 110 3.90 2.620 16.46 0 1 4 4
21.0 160 110 3.90 2.875 17.02 0 1 4 4
22.8 108 93 3.85 2.320 18.61 1 1 4 1
21.4 258 110 3.08 3.215 19.44 1 0 3 1
18.7 360 175 3.15 3.440 17.02 0 0 3 2
18.1 225 105 2.76 3.460 20.22 1 0 3 1

Add rating stars

You can take the icons example a step further, and assign rating stars. For this example, we’re creating HTML content in the data itself, before passing it into gt. This example adapted from reactable.

# note you could use ANY font-awesome logo
# https://fontawesome.com/cheatsheet
rating_stars <- function(rating, max_rating = 5) {
  rounded_rating <- floor(rating + 0.5)  # always round up
  stars <- lapply(seq_len(max_rating), function(i) {
    if (i <= rounded_rating) fontawesome::fa("star", fill= "orange") else fontawesome::fa("star", fill= "grey")
  })
  label <- sprintf("%s out of %s", rating, max_rating)
  div_out <- div(title = label, "aria-label" = label, role = "img", stars)
  
  as.character(div_out) %>% 
    gt::html()
}

mtcars %>% 
  head() %>% 
  mutate(rating = purrr::map(sample(1:5, size = 6, TRUE), rating_stars)) %>% 
  gt()
mpg cyl disp hp drat wt qsec vs am gear carb rating
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Tags and badges

Again we can create a custom function and use purrr::map() to generate the HTML code before passing it into gt. Example adapted from reactable documentation.

add_cyl_color <- function(cyl){
      add_color <- if (cyl == 4) {
        "background: hsl(116, 60%, 90%); color: hsl(116, 30%, 25%);"
      } else if (cyl == 6) {
        "background: hsl(230, 70%, 90%); color: hsl(230, 45%, 30%);"
      } else if (cyl == 8) {
        "background: hsl(350, 70%, 90%); color: hsl(350, 45%, 30%);"
      }
      div_out <- htmltools::div(
        style = paste(
          "display: inline-block; padding: 2px 12px; border-radius: 15px; font-weight: 600; font-size: 12px;",
          add_color
          ),
        paste(cyl, "Cylinders")
      )
      
      as.character(div_out) %>% 
        gt::html()
}

mtcars %>% 
  head() %>% 
  mutate(cylinder = purrr::map(cyl, add_cyl_color)) %>% 
  gt()
mpg cyl disp hp drat wt qsec vs am gear carb cylinder
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
6 Cylinders
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
6 Cylinders
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
4 Cylinders
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
6 Cylinders
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
8 Cylinders
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
6 Cylinders

Badges

We can also use badges in a similar way.

add_badge <- function(x){
      add_color <- if (x == "Paid") {
        "background: hsl(116, 60%, 90%); color: hsl(116, 30%, 25%);"
      } else if (x == "Pending") {
        "background: hsl(230, 70%, 90%); color: hsl(230, 45%, 30%);"
      } else if (x == "Canceled") {
        "background: hsl(350, 70%, 90%); color: hsl(350, 45%, 30%);"
      }
      div_out <- htmltools::div(
        style = paste(
          "display: inline-block; padding: 2px 12px; border-radius: 15px; font-weight: 600; font-size: 12px;",
          add_color
          ),
        x
      )
      
      as.character(div_out) %>% 
        gt::html()
}


orders <- data.frame(
  Order = 2300:2304,
  Created = seq(as.Date("2019-04-01"), by = "day", length.out = 5),
  Customer = sample(rownames(MASS::painters), 5),
  Status = sample(c("Pending", "Paid", "Canceled"), 5, replace = TRUE)
) %>% 
  mutate(Status = purrr::map(Status, add_badge))

orders %>% 
  gt()
Order Created Customer Status
2300 2019-04-01 Lanfranco
Paid
2301 2019-04-02 Van Leyden
Paid
2302 2019-04-03 Da Vinci
Pending
2303 2019-04-04 Caravaggio
Canceled
2304 2019-04-05 Pordenone
Canceled

Expandable sections

You can embed expandable sections with <details> HTML, and we can build up some contents of the details tag with the use of htmltools.

library(htmltools)

source_details <- paste0(
  "<details>", "<summary><strong>Table Key, click to expand</strong></summary>",
  div("cyl: Cylinders"), div("disp: Displacement"), div("hp: Horsepower"),
  "</details"
)

head(mtcars) %>% 
  gt() %>% 
  tab_source_note(source_note = html(source_details))
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
Table Key, click to expand
cyl: Cylinders
disp: Displacement
hp: Horsepower

Bar charts

There are different ways to create bar charts, but the example below is adapted from the reactable documentation. The original source on CSS bars using HTML and CSS.

bar_chart <- function(label, height = "16px", fill = "#00bfc4", background = "white") {
  bar <- glue::glue(
    "<div style='background:{fill};width:{label}%;height:{height};'></div>"
    )
  chart <- glue::glue(
    "<div style='flex-grow:1;margin-left:8px;background:{background};'>{bar}</div>"
  )
  glue::glue(
    "<div style='display:flex;align-items:left';>{chart}</div>"
    ) %>%
  gt::html()
  
}

mtcars %>% 
  head() %>% 
  mutate(
    mpg_val = mpg/max(mpg) * 100,
    mpg_plot = purrr::map(mpg_val, ~bar_chart(label = .x)),
    mpg_plot2 = purrr::map(
      mpg_val, 
      ~bar_chart(label = .x, fill = "#fc5185", background = "#e1e1e1")
      ),
    ) %>% 
  select(cyl, hp, disp, mpg, mpg_plot, mpg_plot2) %>% 
  gt() %>% 
  cols_align(align = "left", columns = vars(mpg_plot)) 
cyl hp disp mpg mpg_plot mpg_plot2
6 110 160 21.0
6 110 160 21.0
4 93 108 22.8
6 110 258 21.4
8 175 360 18.7
6 105 225 18.1

Embed images

The function provides a convenient way to generate an HTML fragment with an image URL. Because this function is currently HTML-based, it is only useful for HTML table output. To use this function inside of data cells, it is recommended that the text_transform() function is used.

r_png_url <- "https://www.r-project.org/logo/Rlogo.png"

dplyr::tibble(
    pixels = px(seq(10, 35, 5)),
    image = seq(10, 35, 5)
  ) %>%
  gt() %>%
  text_transform(
    locations = cells_body(vars(image)),
    fn = function(x) {
      web_image(
        url = r_png_url,
        height = as.numeric(x)
      )
    }
  )
pixels image
10px
15px
20px
25px
30px
35px

More images

You can include multiple images by parsing the url along with purrr::map() or lapply()

tibble::tribble(
  ~team_abb,                                                   ~headshot_href,      ~short_name, ~qbr_total, ~qb_plays,
       "GB",    "https://a.espncdn.com/i/headshots/nfl/players/full/8439.png",     "A. Rodgers",       84.4,       608,
       "KC", "https://a.espncdn.com/i/headshots/nfl/players/full/3139477.png",     "P. Mahomes",       82.9,       710,
      "BUF", "https://a.espncdn.com/i/headshots/nfl/players/full/3918298.png",       "J. Allen",       81.7,       729,
      "TEN",   "https://a.espncdn.com/i/headshots/nfl/players/full/14876.png",   "R. Tannehill",       78.3,       594,
      "MIA",    "https://a.espncdn.com/i/headshots/nfl/players/full/8664.png", "R. Fitzpatrick",       76.9,       324,
       "NO",    "https://a.espncdn.com/i/headshots/nfl/players/full/2580.png",       "D. Brees",       74.6,       428,
      "BAL", "https://a.espncdn.com/i/headshots/nfl/players/full/3916387.png",     "L. Jackson",       73.7,       585,
      "SEA",   "https://a.espncdn.com/i/headshots/nfl/players/full/14881.png",      "R. Wilson",       73.5,       716,
       "TB",    "https://a.espncdn.com/i/headshots/nfl/players/full/2330.png",       "T. Brady",       72.5,       681,
      "CLE", "https://a.espncdn.com/i/headshots/nfl/players/full/3052587.png",    "B. Mayfield",       72.2,       597
  ) %>% 
  gt() %>%
  text_transform(
    locations = cells_body(vars(headshot_href)),
    fn = function(x) {purrr::map(x,~ web_image(url = .x, height = 30))}
  )
team_abb headshot_href short_name qbr_total qb_plays
GB A. Rodgers 84.4 608
KC P. Mahomes 82.9 710
BUF J. Allen 81.7 729
TEN R. Tannehill 78.3 594
MIA R. Fitzpatrick 76.9 324
NO D. Brees 74.6 428
BAL L. Jackson 73.7 585
SEA R. Wilson 73.5 716
TB T. Brady 72.5 681
CLE B. Mayfield 72.2 597